home *** CD-ROM | disk | FTP | other *** search
/ Software Vault: The Gold Collection / Software Vault - The Gold Collection (American Databankers) (1993).ISO / cdr52 / udfs.zip / UDFS.TXT < prev   
Text File  |  1993-04-02  |  34KB  |  747 lines

  1.                               THE ABC's OF UDF's
  2.  
  3.                                  By Greg Lief
  4.  
  5.  
  6. I.   INTRODUCTION AND BACKGROUND
  7.  
  8.      A.  Clipper vs. dBASE
  9.  
  10.          Do you remember the first time you saw or heard about Clipper?
  11.          Chances are that your first impression of it was that of a
  12.          dBASE compiler.  That feature alone makes Clipper worthwhile.
  13.          However, as you take the time to delve into Clipper, you
  14.          quickly realize that there is more to Clipper than just a
  15.          "dBASE compiler".  The Clipper language addresses nearly all
  16.          of the shortcomings of the dBASE language by providing numerous
  17.          additional functions and commands.  But more important than all
  18.          those new goodies is Clipper's open architecture, which allows
  19.          you to write and/or link in routines written in "C", Assembler,
  20.          and (surprise!) Clipper.  Such Clipper routines are known as
  21.          User-Defined Functions.
  22.  
  23.          User-Defined Functions (UDFs) open up an entirely new avenue of
  24.          options that are simply inconceivable in a dBASE environment.
  25.          For those of you that have not taken the time to write your own
  26.          UDFs already, think of them as your own personal language
  27.          extensions.  Plus, you do not have to learn another (often
  28.          cryptic) language to write them.
  29.  
  30.      B.  Benefits of UDFs
  31.  
  32.          A few basic benefits of UDFs include:
  33.  
  34.          - getting the absolute most out of the wonderfully open-ended
  35.            Clipper language;
  36.  
  37.          - minimizing repetitious coding in your applications, thus
  38.            reducing overhead requirements, maintenance time/cost/
  39.            frustration, and increasing efficiency;
  40.  
  41.          - allowing you to develop your own libraries of UDFs rather
  42.            than having to reinvent the wheel with each new occurrence
  43.            of the same old problem, thereby increasing productivity;
  44.  
  45.          - achieving consistency among your applications by using the
  46.            same functions "across the board", which decreases confusion
  47.            (both for your clients and you);
  48.  
  49.          A side benefit to UDFs is that they gently but firmly force you
  50.          to adopt better structured programming techniques.  For a
  51.          language (dBASE, and hence Clipper) that can be very
  52.          unstructured, we need all the guidance we can get!  UDFs
  53.          encourage modular programming, rather than throwing everything
  54.          into one procedure and hoping you will never have to debug it.
  55.  
  56.      C.  A Few Examples
  57.  
  58.          Some of the myriad purposes for User-Defined Functions include:
  59.  
  60.          1. Data validation
  61.  
  62.             Without UDFs, you are limited to useful (but simple) VALID
  63.             clauses, or RANGE checking.
  64.  
  65.             With UDFs, you can perform complex look-ups into related
  66.             databases or arrays, and display verbose descriptions found
  67.             therein.  You can also provide the user meaningful error
  68.             messages in the event that their data does not pass muster,
  69.             rather than having them sit there wondering why "the cursor
  70.             won't move".
  71.  
  72.          2. Common Occurrences, such as yes/no prompts, error messages,
  73.             or displaying file directories for file-related operations
  74.  
  75.             Without UDFs, you are faced with redundant code, which leads
  76.             to difficult maintenance.
  77.  
  78.             With UDFs, you have the code in just one place.  This is a
  79.             blessing when you decide that those green on magenta yes/no
  80.             prompts are not as pleasant as you originally thought.  Now
  81.             you can change it once instead of ten (or more) times.
  82.  
  83.          3. Centering character strings on the screen or printer
  84.  
  85.             Without UDFs, you have to type in the cumbersome
  86.             "INT((80-LEN(string))/2)" formula throughout your program.
  87.             Don't forget the last parenthesis, or the compiler will
  88.             scream at you!
  89.  
  90.             With UDFs, you waste no time retyping said formula, which
  91.             results in cleaner code and a clearer head.
  92.  
  93.          4. Cosmetic enhancements
  94.  
  95.             Without UDFs, you have the same old humdrum database
  96.             management look and feel (or should we call it "yawn and
  97.             stretch"?), which some people like and most people are
  98.             resigned to.
  99.  
  100.             With UDFs, you can have exploding / pull-down / pop-up
  101.             boxes, falling character strings, splitting screens, and all
  102.             kinds of other crazy things that will get your users'
  103.             attention IMMEDIATELY.
  104.  
  105.          5. "Hot-key" procedures, including pop-up help screens and
  106.             many other utilities
  107.  
  108.             Without UDFs, such utilities are impossible and unthinkable.
  109.  
  110.             With UDFs, the sky (and your imagination) is the limit!
  111.  
  112.  
  113. II.  UDF BASICS
  114.  
  115.      A.  Structure
  116.  
  117.          Consider the following skeleton of a (recently deceased) UDF:
  118.  
  119.          FUNCTION funcname
  120.          PARAMETERS param1, param2, ...
  121.          PRIVATE return_value, etc...
  122.          |
  123.          | code to manipulate data
  124.          |
  125.          RETURN (return_value)
  126.  
  127.          i.   Name that Function, and Grab Those Parameters!
  128.  
  129.               First, we name the UDF using the FUNCTION (or PROCEDURE)
  130.               statement.  Next, the PARAMETERS statement is used to
  131.               receive any parameters that will be sent to this UDF from
  132.               the calling program. Note that some UDFs will not require
  133.               the use of parameters, in which case the PARAMETERS
  134.               statement may be cheerfully omitted.
  135.  
  136.          ii.  PRIVATE Variables
  137.  
  138.               The next step is to declare PRIVATE all variables that will
  139.               be "local" to this UDF.  Why bother with this?  There are
  140.               two reasons.  First, all variables available, or "visible",
  141.               to the calling program will also be available to the UDF.
  142.               Therefore, any changes or assignments made to variables
  143.               within the UDF will affect such variables in the context of
  144.               main program as well UNLESS YOU DECLARE THEM PRIVATE TO THE
  145.               UDF.
  146.  
  147.               This point is quite important, and is the source of many
  148.               subtle bugs if not heeded.  Suppose that you have defined a
  149.               variable named OLDSCRN in your main program that holds a
  150.               crucial saved screen. Now further suppose that you wish
  151.               your UDF to display a message or a scrolling window,
  152.               thereby affecting the screen.  You would probably want to
  153.               save that portion of the screen to a buffer so that the UDF
  154.               could then restore it properly upon exit.  Finally, suppose
  155.               that you save that window to the variable OLDSCRN within
  156.               the UDF without declaring it as PRIVATE to the UDF.  No
  157.               local copy of the variable will be made, which means that
  158.               you will overwrite the previous value of OLDSCRN, which
  159.               will almost certainly wreak havoc once you return to the
  160.               calling program.
  161.  
  162.               Another secondary reason for declaring variables PRIVATE
  163.               within a UDF is so that you can keep track of what
  164.               variables you are using in your application and where they
  165.               are being used.
  166.  
  167.          iii. The Meat of The UDF
  168.  
  169.               This consists of the code that will manipulate the data, or
  170.               cure cancer, or whatever it is that this UDF is intended to
  171.               do.
  172.  
  173.          iv.  A Speedy RETURN
  174.  
  175.               We wrap up the UDF with a RETURN statement.  With
  176.               functions, this statement MUST return a value back to the
  177.               calling program (even if this value will be ignored).
  178.  
  179.               It is strongly advised that you follow the practice of
  180.               having only one RETURN statement in your functions, rather
  181.               than something like the following:
  182.  
  183.               FUNCTION myudf
  184.               PARAMETER mNAME, mID
  185.               IF mNAME = 'GRUMPFISH'
  186.                  RETURN (.T.)
  187.               ELSEIF mNAME = 'HAPPYFISH"
  188.                  RETURN (.F.)
  189.               ELSEIF mID = '99999'
  190.                  RETURN (.T.)
  191.               ENDIF
  192.  
  193.               As you can see from this substandard piece of code, there
  194.               are no less than three exit points from this UDF.  Although
  195.               this is a fairly simple example, multiple exit points can
  196.               can make debugging very difficult when you begin working
  197.               with UDFs of greater complexity.  The following is a
  198.               drastic improvement:
  199.  
  200.               FUNCTION myudf
  201.               PARAMETER mNAME, mID
  202.               PRIVATE ret_val
  203.               ret_val = .F.         && guilty until proven innocent
  204.               IF mNAME = 'GRUMPFISH' .OR. mID = '99999'
  205.                  ret_val = .T.
  206.               ENDIF
  207.               RETURN (ret_val)
  208.  
  209.      B.  FUNCTION or PROCEDURE?
  210.  
  211.          The term "User-Defined Function" refers not only to functions,
  212.          but to procedures as well.  The fundamental difference between
  213.          these two beasts is that functions return a value, whereas
  214.          procedures do not.  However, Clipper allows you to begin a
  215.          program statement with a function [such as the ever-popular
  216.          INKEY(0)], in which case the return value is ignored. You may
  217.          also have functions that always return the same value, with the
  218.          express intent that you will ignore that value.
  219.  
  220.          Other differences exist as well, particularly relating to
  221.          parameters, which we will explore momentarily...
  222.  
  223.  
  224. III. PARAMETERS
  225.  
  226.      A.  Introduction
  227.  
  228.          There are some functions that always return one value, such as
  229.          TIME() and DATE().  These functions have a predefined mission
  230.          that cannot be altered by sending them additional information.
  231.          However, when you write your own UDFs, you will most likely want
  232.          to exert more control over them.  For example, if you design a
  233.          UDF that pops up an error box, you may initially write it to use
  234.          a generic message such as "Error - press any key to continue".
  235.          Chances are that you (and your users) will quickly tire of this
  236.          uninformative error message. In this instance, whenever you call
  237.          the UDF you would want to pass it a message to be displayed.
  238.          You could then easily construct the UDF to act upon the passed
  239.          message to draw a box wide enough to accommodate it.  This
  240.          message is known as a PARAMETER.
  241.  
  242.      B.  Formal vs. Actual
  243.  
  244.          Parameters are memory variables that receive values or
  245.          references passed to a function or procedure.  Parameters are
  246.          known as either "formal" or "actual".  Formal parameters are
  247.          the receiving memory variables specified as the arguments of
  248.          the PARAMETERS statement.  Actual parameters are the arguments
  249.          of the call to the procedure (DO..WITH) or UDF [MyUdf(...)].
  250.          Note that the number of formal and actual parameters do not
  251.          have to match.
  252.  
  253.          Let us use the following example to explore the various
  254.          ramifications of having more or less actual parameters than
  255.          formal parameters.  Here is the UDF we will use:
  256.  
  257.          FUNCTION MyFunc
  258.          PARAMETERS mvar1, mvar2, mvar3, mvar4
  259.          PRIVATE ret_val
  260.          ret_val = ((mvar1 * 2.75) + (mvar2 * mvar3)) / (mvar4 + 5)
  261.          RETURN (ret_val)
  262.  
  263.          As you can see, MyFunc() is an obtuse number-cruncher.  It
  264.          accepts four parameters, performs a strange calculation
  265.          involving them, and returns the value of said calculation.
  266.  
  267.          What happens when we call MyFunc() with:
  268.  
  269.          1)  "result = MyFunc(10, 5, 7, 20)"
  270.  
  271.              The number of actual and formal parameters match exactly.
  272.              MVAR1 assumes the value 10, MVAR2 assumes the value 5,
  273.              MVAR3 assumes the value 7, and MVAR4 assumes the value of
  274.              20.  For those of you keeping score, the return value is
  275.              2.5, which will be stored in RESULT.
  276.  
  277.          2)  "result = MyFunc(10, 5, 7, 20, 76, 120)"
  278.  
  279.              Here we are passing six actual parameters.  Since there
  280.              are only four parameters in the formal list, the function
  281.              will only act upon the first four actual parameters.  As
  282.              in example 1), MVAR1 assumes the value 10, MVAR2 assumes
  283.              the value 5, MVAR3 assumes the value 7, and MVAR4 assumes
  284.              the value of 20.  The parameters 76 and 120 are discarded.
  285.  
  286.          3)  "result = MyFunc(10, 5, 7)"
  287.  
  288.              In this example we pass only three actual parameters. With
  289.              MyFunc(), this is asking for trouble because the UDF
  290.              explicitly acts upon all four parameters.  You can see
  291.              that, as above, MVAR1 assumes the value 10, MVAR2 assumes
  292.              the value 5, and MVAR3 assumes the value 7. However, MVAR4
  293.              will be undefined since we have not passed a fourth
  294.              parameter to match it.  This will cause MyFunc() to crash
  295.              when it reaches the calculation statement with the message
  296.              "unidentified identifier MVAR4".
  297.  
  298.      C.  Number and Type Checking
  299.  
  300.          That little crash in Example 3 could have been avoided using
  301.          the Clipper function PCOUNT(), which checks the number of
  302.          actual parameters passed.  To make our little number-cruncher a
  303.          bit more bulletproof, we could rewrite it as follows:
  304.  
  305.          FUNCTION MyFunc
  306.          PARAMETERS mvar1, mvar2, mvar3, mvar4
  307.          PRIVATE ret_val
  308.          ret_val = 0
  309.          ** did they pass all four parameters?
  310.          IF PCOUNT() > 3
  311.             ret_val = ((mvar1 * 2.75) + (mvar2 * mvar3)) / (mvar4 + 5)
  312.          ELSE
  313.             ** guess not - time for sound and fury
  314.             ? 'Error in call to MyFunc()'
  315.             TONE(220,1)
  316.             TONE(220,1)
  317.             INKEY(0)
  318.          ENDIF
  319.          RETURN (ret_val)
  320.  
  321.          However, suppose that we are coding late one night, and we are
  322.          very tired.  So tired, in fact, that we make a careless mistake
  323.          like the following:
  324.  
  325.          STORE 10 TO val1, val2, val3, val4
  326.          |
  327.          val3 = 'Could be trouble'
  328.          |
  329.          result = MyFunc(val1, val2, val3, val4)
  330.  
  331.          When we finally get to MyFunc(), the variable MVAR3 will assume
  332.          the value of VAL3, which has inadvertently been defined as a
  333.          character string.   This will cause a "Type Mismatch" explosion
  334.          when MyFunc() attempts to perform a numeric operation on a
  335.          string. However, we can avoid this as well by using the Clipper
  336.          function TYPE(). Obviously enough, TYPE() checks the type of a
  337.          variable that is passed to it in the form of a character
  338.          string. For example, in this instance "TYPE('val3')" would
  339.          return a value of "C" for character, because VAL3 is a
  340.          character-type variable.
  341.  
  342.          With the TYPE() function under our belts, let us take one more
  343.          stab at MyFunc():
  344.  
  345.          FUNCTION MyFunc
  346.          PARAMETERS mvar1, mvar2, mvar3, mvar4
  347.          PRIVATE ret_val, err_msg
  348.          ret_val = 0
  349.          ** did they pass all four parameters?
  350.          IF PCOUNT() > 3
  351.             ** are all parameters numeric type?
  352.             IF TYPE('mvar1') = 'N' .AND. TYPE('mvar2') = 'N' .AND. ;
  353.                TYPE('mvar3') = 'N' .AND. TYPE('mvar4') = 'N'
  354.                ret_val = ((mvar1*2.75) + (mvar2*mvar3)) / (mvar4+5)
  355.             ELSE
  356.                err_msg = 'Type mismatch in parameters to MyFunc()'
  357.             ENDIF
  358.          ELSE
  359.             err_msg = 'Not enough parameters passed to MyFunc()'
  360.          ENDIF
  361.          ** see if error message was defined - if so, display it
  362.          IF TYPE('err_msg) = 'C'
  363.             ? err_msg
  364.             TONE(220,1)
  365.             TONE(220,1)
  366.             INKEY(0)
  367.          ENDIF
  368.          RETURN (ret_val)
  369.  
  370.          As you can see, the MyFunc() code continues to grow, but it is
  371.          now virtually bullet-proof.  In this incarnation, you can call
  372.          from it anywhere in your program without having to worry about
  373.          the program crashing because of incorrect parameters.  This is
  374.          good programming practice for you to follow when creating your
  375.          own UDFs for several reasons:
  376.  
  377.          1)  Some other programmer may eventually use your UDF,
  378.              especially if you are working in a team environment, and
  379.              you should make every attempt to shield them from fatal
  380.              (i.e., immediate exit to DOS) errors.
  381.  
  382.          2)  Nobody is perfect, and it is entirely possible that at some
  383.              future time you too could accidentally call a UDF with the
  384.              wrong parameters, so why not shield yourself as well?
  385.  
  386.          Also note the use of meaningful error messages based on the
  387.          type of error.  This is not exactly earth-shattering but may
  388.          save someone valuable time when debugging.
  389.  
  390.      D.  Reference vs. Value
  391.  
  392.          When a parameter is passed by VALUE, the UDF evaluates it and
  393.          makes a "local" copy of the resultant value at a different
  394.          memory address.  Whenever the UDF needs to work with that
  395.          parameter, it refers to the new memory address rather than
  396.          using the memory address of the original parameter (which is
  397.          unknown to it).  This is ideal when you want the UDF to
  398.          manipulate or tear asunder some variable without affecting its
  399.          value once control returns to the calling program.
  400.  
  401.          "And in this corner..." passing parameters by REFERENCE means
  402.          that instead of passing the value of a variable, you instead
  403.          are passing a pointer to a memory address that actually
  404.          contains the variable.  Unlike parameters passed by value, no
  405.          "local" copy is made; thus if you change that parameter within
  406.          the UDF, you are effectively changing the actual value of that
  407.          variable.
  408.  
  409.          Memory variables are passed by reference to PROCEDURES, and by
  410.          value to FUNCTIONS.  Consider the following example:
  411.  
  412.          * Main.prg
  413.          mvar = 50
  414.          DO MultByFive WITH mvar
  415.          ? mvar
  416.          RETURN
  417.          *
  418.          PROCEDURE MultByFive
  419.          PARAMETERS testing
  420.          testing = testing * 5
  421.          RETURN
  422.  
  423.          Since variables are passed by reference to PROCEDURES,
  424.          MultByFive is actually changing the value of the variable MVAR.
  425.          When we return to Main.prg, the value of MVAR will be 250,
  426.          rather than 50.  However, the equivalent FUNCTION would allow
  427.          you to manipulate this variable while protecting it in the
  428.          calling program:
  429.  
  430.          * Main.prg
  431.          mvar = 50
  432.          ? MultByFive(mvar)
  433.          ? mvar
  434.          RETURN
  435.          *
  436.          FUNCTION MultByFive
  437.          PARAMETERS testing
  438.          testing = testing * 5
  439.          RETURN (testing)
  440.  
  441.          Because parameters are passed by value to FUNCTIONS, MultByFive
  442.          evaluates MVAR and places that value in a different memory
  443.          address, which it then refers to as TESTING.  When it
  444.          multiplies TESTING by 5, it is only changing the "local" copy
  445.          of the variable rather than manipulating MVAR.  Thus, when
  446.          control returns to Main.prg, the value of MVAR will still be
  447.          50.
  448.  
  449.          There are instances where you may wish to override these basic
  450.          defaults.  For example, one good reason to pass variables by
  451.          reference to FUNCTIONS is speed.  It takes time for the
  452.          FUNCTION to create the local copy of the variable.  Passing by
  453.          reference, however, eliminates this need, and can increase
  454.          throughput by an enormous factor.  To pass a variable by
  455.          reference to a function, simply precede it with the "AT" sign
  456.          (@).  For instance, in the last example we could have used the
  457.          statement "? MultByFive(@mvar)".  Bear in mind that this would
  458.          have caused MultByFive() to change the value of MVAR to 250.
  459.  
  460.          In similar fashion, you can pass variables to PROCEDURES by
  461.          value.  This is useful if you do not want the procedure to
  462.          inadvertently change the value of a variable in the calling
  463.          program.  Had we chosen this route in our PROCEDURE-al example
  464.          above, the syntax would have been "DO MultByFive WITH (mvar)".
  465.          Of course, we would probably have modified MultByFive to
  466.          display the calculated value, or else it would be an exercise
  467.          in futility.
  468.  
  469.          Database fields are always passed by value to FUNCTIONS and
  470.          PROCEDURES, and must be bounded by parentheses.  Array names
  471.          are always passed by reference.  This is sensible, because it
  472.          would be incredibly time-consuming to pass a large array to a
  473.          UDF, only to have the UDF then make a copy of it for local
  474.          manipulation.  Array elements and expressions are always
  475.          passed by value.
  476.  
  477.  
  478. IV.  HOUSEKEEPING
  479.  
  480.      If you are designing a UDF that in any way changes the
  481.      environment (color, screen, coordinates, work area, cursor
  482.      status), it is good practice to save all items that will
  483.      be changed upon entry of the UDF, and restore them upon
  484.      exit.  The following code fragment illustrates this:
  485.  
  486.      FUNCTION look_up
  487.      PRIVATE oldscrn, oldcolor, work_area, oldrow, oldcol, oldcurs
  488.      ** save environment
  489.      SAVE SCREEN TO oldscrn
  490.      oldcolor = SETCOLOR()
  491.      work_area = SELECT()
  492.      oldrow = ROW()
  493.      oldcol = COL()
  494.      oldcurs = IsCursor()
  495.      |
  496.      | code to manipulate data
  497.      |
  498.      ** restore environment
  499.      RESTORE SCREEN FROM oldscrn
  500.      SETCOLOR(oldcolor)
  501.      SELECT(work_area)
  502.      SET CURSOR (oldcurs)
  503.      @ oldrow, oldcol SAY ''
  504.      RETURN(return_value)
  505.  
  506.      This particular UDF is going to be used to look up values in a
  507.      related database, and will change the entire environment
  508.      (screen, color, work area, etc.).  Therefore, we must save the
  509.      current settings so that we can restore them properly upon
  510.      exit.
  511.  
  512.      First, we declare our local variables as PRIVATE to avoid
  513.      potential conflicts with other variables of the same names.
  514.      Next, we use the Clipper command SAVE SCREEN to save the
  515.      screen, because we intend to change it.  SETCOLOR() returns the
  516.      current color setting, which we must save because we might
  517.      change the color.  SELECT() returns the current work area.
  518.      ROW() and COL() return the current screen row and column
  519.      coordinates, respectively.  IsCursor() is a public domain
  520.      routine written by John Scott Prinke, which returns the current
  521.      state of the cursor as a logical value (.T. means on).
  522.  
  523.  
  524. V.   HOT-KEY PROCEDURES
  525.  
  526.      A.  Activating the Hot Key
  527.  
  528.          A "hot-key" procedure is one that is activated by a designated
  529.          keypress.  To create and use a hot-key procedure, you must
  530.          first define the hot-key in your main program in this manner:
  531.  
  532.              * Main.prg
  533.  
  534.              EXTERNAL <hotkey UDF>         && not always necessary
  535.              SET KEY <nn> TO <hotkey UDF>  && assign hot key to the UDF
  536.  
  537.          A complete listing of scan codes for all of the function key,
  538.          Ctrl-key, and Alt-key combinations can be found in your Clipper
  539.          manual.
  540.  
  541.      B.  External
  542.  
  543.          The EXTERNAL command must be used if you intend to link in a
  544.          routine from a library rather than from an object module.  The
  545.          reason for EXTERNAL stems from the modus operandi of the
  546.          Clipper compiler.  When you call a procedure with "DO
  547.          procname", the compiler creates a symbol named PROCNAME.  If
  548.          this symbol remains unresolved before link-time, (i.e., the
  549.          compiler does not find it in any of the compiled .prg files),
  550.          the linker will first attempt to resolve it by searching the
  551.          other object files that you are linking in.  If it is
  552.          unsuccessful, it will then search through the specified
  553.          libraries.  However, the compiler does not create a symbol when
  554.          you use the SET KEY command.  For example, you wish to link in
  555.          the Grumpfish Library appointment tracker and set the F10 key
  556.          to activate it.  You forget about EXTERNAL, and only use:
  557.  
  558.               SET KEY -9 TO popdate
  559.  
  560.          This will look fine through compilation and linking, but as
  561.          soon as you get into the program and begin pressing F10, you
  562.          will get the nasty run-time error "MISSING EXTERNAL POPDATE".
  563.          As Spock would readily point out, this is perfectly logical,
  564.          because you forgot to instruct the linker to link in this
  565.          module.
  566.  
  567.      C.  Basic Structure
  568.  
  569.          The structure of a hot-key procedure is similar to that of a
  570.          UDF, with several key differences (pun intentional):
  571.  
  572.          PROCEDURE <procname>
  573.          PARAMETERS proc, line, var    && built-in Clipper parameters!
  574.          SET KEY <nn> TO               && to prevent recursion
  575.          |
  576.          | code to manipulate data
  577.          |
  578.          SET KEY <nn> TO <hotkey UDF>  && reset for later use
  579.          RETURN
  580.  
  581.          1. Built-in Clipper PARAMETERS
  582.  
  583.             The PARAMETERS line is advisable, because whenever you
  584.             execute a SET KEY procedure, Clipper automatically passes
  585.             three parameters to the procedure:
  586.  
  587.             -  Procedure Name (always upper-case), Source Code
  588.             -  Line Number (0 if the source code was compiled with
  589.                the "line numbers off" switch)
  590.             -  Variable Name being READ (always upper-case)
  591.  
  592.             You do not necessarily have to trap these parameters, but in
  593.             many cases they may be useful to know.  One instance where
  594.             you would definitely want this information is if you are
  595.             building a context-specific help procedure, such as the
  596.             Grumpfish Help System.  You may wish to note, however, that
  597.             since line numbers are quite volatile, you should not rely
  598.             too heavily upon them in a help system.  As a matter of
  599.             fact, the Grumpfish Help System completely ignores line
  600.             numbers, acting instead only upon the procedure and variable
  601.             names.
  602.  
  603.          2. Recursion and How to Avoid It
  604.  
  605.             In the line after the PARAMETERS line, we use SET KEY <.>
  606.             TO.  This prevents recursion - otherwise, the user could
  607.             continue to press the hot-key from within the hot-key
  608.             procedure, which would then call itself repeatedly until
  609.             both you and it would be thoroughly confused.  The last line
  610.             before the RETURN statement resets the hot key.
  611.  
  612.             If you are using a number of hot keys, you may wish to
  613.             consider creating UDFs that will turn all of the hot keys on
  614.             and off. You could then call those UDFs to prevent the user
  615.             from "hot-keying" willy-nilly through the program:
  616.  
  617.              PROCEDURE <procname>
  618.              PARAMETERS proc, line, var    && built-in Clipper parameters!
  619.              HotKeysOff()
  620.              |
  621.              | code to manipulate data
  622.              |
  623.              HotKeysOn()
  624.              RETURN
  625.  
  626.      D.  Housekeeping
  627.  
  628.          Nearly all hot-key procedures will change the environment in
  629.          some manner, which makes the aforementioned good housekeeping
  630.          techniques even more crucial.  Get in the habit of saving
  631.          volatile items like the current color and affected portions of
  632.          the screen right at the top of your hot-key procedures.
  633.  
  634.  
  635. VI.  MAKING YOUR OWN UDF LIBRARY
  636.  
  637.      So you have built up an impressive collection of fully debugged
  638.      UDFs.  Great!  But aren't you getting a bit tired of typing all
  639.      those object (.OBJ) modules on the link line, and aren't your
  640.      directories getting a bit cluttered?  The best way to kill both of
  641.      these birds with one stone is to consolidate all of your UDFs into
  642.      a library (.LIB) file with the Microsoft Library Manager (TM).
  643.  
  644.      A. Using the Library Manager
  645.  
  646.         The Microsoft Library Manager is pre-packaged with a number of
  647.         other Microsoft products, including their C compiler and some
  648.         versions of DOS.  (Look for the file LIB.EXE in your DOS
  649.         directory or on your DOS supplemental disk.)  Using LIB to
  650.         create and modify your own function libraries is simple.  The
  651.         syntax for LIB is:
  652.  
  653.              LIB <Libname> <Commands>, <Listfile>, <Output>
  654.  
  655.         <Libname> is the name of the library (the .lib extension is not
  656.         necessary).
  657.  
  658.         <Commands> are of the general format <symbol>filename, and must
  659.         be separated by spaces.  The available symbols include:
  660.  
  661.             +    add modulename to the library
  662.             -    remove modulename from the library
  663.             *    extract modulename without removing from library
  664.             -+   replace modulename in library
  665.             -*   extract modulename and remove from library
  666.  
  667.         <Listfile> is the name of the list file to be generated, which
  668.         lists the module names in the library and the memory usage for
  669.         each module.
  670.  
  671.         <Output> is the name of the output library file.  This is useful
  672.         if you wish to make a new library that essentially duplicates
  673.         another with some modifications.
  674.  
  675.         If you do not wish to generate a list file or a new output
  676.         library file, you may follow the command list with a semi-
  677.         colon.
  678.  
  679.         Note that you may also run LIB without command line parameters,
  680.         in which instance you will be prompted for each item.
  681.  
  682.      B. Preparing your source code
  683.  
  684.         The most efficient way to build a library is to compile each of
  685.         your UDFs separately, excepting instances where you know that
  686.         certain UDFs always must be linked in together.  The reason for
  687.         isolating each UDF is because when you call the library with
  688.         your linker, the linker will pull in only what you have asked
  689.         for in your source code.
  690.  
  691.         Let us suppose that you have called the pop-up calculator from
  692.         Grumpfish Library with the source code line "DO POPCALC".  When
  693.         you compile this source code, the compiler creates the symbol
  694.         POPCALC, which the linker will then attempt to resolve by
  695.         searching the libraries for an object module of that name.
  696.         However, because I compiled each of my UDFs separately before
  697.         creating Grumpfish Library, the linker will only link in
  698.         POPCALC, rather than pulling in the entire library.
  699.  
  700.         By contrast, imagine that you have ten short UDFs in one .prg
  701.         file. You compile this file and put it into a library.  If you
  702.         call any one of those ten UDFs, the linker will be forced to
  703.         link in ALL of them because they are all part of the same object
  704.         module.  This is additional overhead that you simply do not
  705.         need.  Therefore, you should take the time to compile each UDF
  706.         separately.
  707.  
  708.      C. Examples
  709.  
  710.         Let us suppose that you have four object files: LOOKUP.obj,
  711.         REC_LOCK.obj, REC_SRCH.obj, and CENTER.obj.  You wish to
  712.         combine these into one .lib file named MYFUNCS.lib.  You do
  713.         not need a list file, nor do you need a different name for
  714.         the output library.  Here is the command you would use:
  715.  
  716.              LIB myfuncs +lookup +rec_lock +rec_srch +center ;
  717.  
  718.         This will create MYFUNCS.LIB, which will contain the four
  719.         object files.  Then instead of linking in each .obj file like
  720.         so:
  721.  
  722.         PLINK86 FI myprog, dup_chk, rec_lock, rec_srch, center ;
  723.                 LI \clipper\extend,\clipper\clipper
  724.  
  725.         you merely link in the library:
  726.  
  727.         PLINK86 FI mfile LI myfuncs,\clipper\extend,\clipper\clipper
  728.  
  729.         Suppose that later you make changes to the source code of
  730.         REC_LOCK.prg and need to recompile it.  You would then update
  731.         MYFUNCS.LIB with the following command:
  732.  
  733.         LIB myfuncs -+rec_lock;
  734.  
  735.         This removes the module REC_LOCK from the library, then adds in
  736.         the newer version.  Naturally, you must have the file
  737.         REC_LOCK.OBJ in the same directory, or else you must add the
  738.         path specifier so that LIB can find it.
  739.  
  740.         If you decided later that you wanted to extract the object code
  741.         for REC_LOCK, you would use this command:
  742.  
  743.         LIB myfuncs *rec_lock;
  744.  
  745.         This would extract the file REC_LOCK.OBJ without removing it
  746.         from the MYFUNCS library.
  747.